package com.starview.platform.data.type;

import com.starview.star.compiler.standard.StandardNames;
import com.starview.star.compiler.type.Dictionary;
import com.starview.star.compiler.util.FixedList;
import com.starview.star.compiler.util.PrettyPrintDisplay;
import com.starview.star.compiler.util.StringUtils;

@SuppressWarnings("serial")
public class TypeAlias implements ITypeAlias
{
  private final Location loc;
  private final IType alias;
  private final Type typeCon;

  private TypeAlias(Location loc, Type typeCon, IType alias)
  {
    this.loc = loc;
    this.typeCon = typeCon;
    this.alias = alias;
  }

  public TypeAlias(Location loc, IType alias)
  {
    this(loc, (Type) TypeUtils.getTypeCon(TypeUtils.getTypeArg(TypeUtils.unwrap(alias), 0)), alias);
  }

  public static TypeAlias typeAlias(Location loc, String label, IType bound)
  {
    return typeAlias(loc, label, Kind.type, bound);
  }

  public static TypeAlias typeAlias(Location loc, String label, Kind kind, IType bound)
  {
    Type typeCon = new Type(label, kind);
    IType alias = TypeUtils.typeExp(StandardNames.ALIAS, typeCon, bound);
    return new TypeAlias(loc, typeCon, alias);
  }

  public static TypeAlias typeAlias(Location loc, String label, int arity, IType bound)
  {
    assert bound.kind().mode() == Kind.Mode.typefunction;
    IType[] args = new IType[arity];
    for (int ix = 0; ix < arity; ix++)
      args[ix] = new TypeVar();
    Type typeCon = new Type(label, Kind.kind(arity));

    IType alias = TypeUtils.typeExp(StandardNames.ALIAS, new TypeExp(typeCon, args), new TypeExp(bound, args));
    return new TypeAlias(loc, typeCon, alias);
  }

  @Override
  public int typeArity()
  {
    return typeCon.getArity();
  }

  @Override
  public Type getType()
  {
    return typeCon;
  }

  @Override
  public Kind kind()
  {
    return typeCon.kind();
  }

  @Override
  public IType apply(IType tp, Location loc, Dictionary cxt) throws TypeConstraintException
  {
    tp = TypeUtils.deRef(tp);

    if (tp.typeLabel().equals(typeCon.typeLabel())) {
      int actualArity = TypeUtils.typeArity(tp);
      int expectedArity = typeCon.getArity();
      if (actualArity == expectedArity) {
        IType refreshed = Freshen.freshenForUse(alias);
        assert TypeUtils.isType(refreshed, StandardNames.ALIAS, 2);
        IType[] ptnArgs = TypeUtils.typeArgs(TypeUtils.getTypeArg(refreshed, 0));
        IType[] tpArgs = TypeUtils.typeArgs(tp);

        for (int ix = 0; ix < ptnArgs.length; ix++)
          Subsume.subsume(tpArgs[ix], ptnArgs[ix], loc, cxt, true);

        return TypeUtils.getTypeArg(refreshed, 1);
      } else if (actualArity != 0)
        throw new TypeConstraintException(FixedList.create(tp.typeLabel(), " expects ", expectedArity,
            " type argument", StringUtils.pluralize(expectedArity, "", "s"), ", ", actualArity, StringUtils.pluralize(
                actualArity, " was", " were"), " given"), loc);
      else
        return tp;
    } else
      throw new TypeConstraintException(StringUtils.msg("type ", tp, " not an instance of type alias ", typeCon), loc);
  }

  public IType alias()
  {
    return alias;
  }

  @Override
  public void verifyType(IType type, Location loc, Dictionary dict) throws TypeConstraintException
  {
  }

  @Override
  public Location getLoc()
  {
    return loc;
  }

  @Override
  public String getName()
  {
    return typeCon.typeLabel();
  }

  @Override
  public void prettyPrint(PrettyPrintDisplay disp)
  {
    IType tp = TypeUtils.unwrap(alias);

    assert TypeUtils.isType(tp, StandardNames.ALIAS, 2);
    IType lhs = TypeUtils.getTypeArg(tp, 0);
    IType rhs = TypeUtils.getTypeArg(tp, 1);

    disp.appendWord(StandardNames.TYPE);
    DisplayType.display(disp, lhs);
    disp.append(" is alias of ");
    DisplayType.display(disp, rhs);
  }

  @Override
  public String toString()
  {
    return PrettyPrintDisplay.toString(this);
  }
}
